#ifndef __BUFFER_H__
#define __BUFFER_H__

#include <vector>
#include <assert.h>
#include <algorithm>
#include <cstring>
#include <string>
#include "Base/noncopyable.h"

namespace tiny_muduo
{
    static const size_t kPrePendIndex = 8;
    static const size_t kInitialSize = 1024;
    static const char* kCRLF = "\r\n";
    
    class Buffer : public NonCopyable
    {
    public:
        Buffer(size_t initBuffSize = kInitialSize) 
            : buffer_(kPrePendIndex + initBuffSize), 
            read_index_(kPrePendIndex), 
            write_index_(kPrePendIndex) 
        {
            assert(readablebytes() == 0);
            assert(writablebytes() == initBuffSize);
            assert(prependablebytes() == kPrePendIndex);
        }
        ~Buffer() = default;

        void swap(Buffer& rhs)
        {
            buffer_.swap(rhs.buffer_);
            std::swap(read_index_, rhs.read_index_);
            std::swap(write_index_, rhs.write_index_);
        }

        ssize_t ReadFd(int fd);

        char* beginWrite() { return begin() + write_index_; }
        const char* beginWrite() const { return begin() + write_index_; }

        const char* FindCRLF() const 
        {
            const char* crlf = std::search(Peek(), beginWrite(), kCRLF, kCRLF + 2);
            return crlf == beginWrite() ? nullptr : crlf;
        }

        const char* FindCRLF(const char* start) const
        {
            assert(Peek() <= start);
            assert(start <= beginWrite());
            const char* crlf = std::search(start, beginWrite(), kCRLF, kCRLF + 2);
            return crlf == beginWrite() ? nullptr : crlf;
        }

        void Append(const char* message)
        {
            Append(message, strlen(message));
        }

        void Append(const char* message, int len)
        {
            MakeSureEnoughStorage(len);
            std::copy(message, message + len, beginWrite());
            write_index_ += len;
        }

        void Append(const std::string& message)
        {
            Append(message.data(), message.size());
        }

        void prepend(const void* /*restrict*/ data, size_t len)
        {
            assert(len <= prependablebytes());
            read_index_ -= len;
            const char* d = static_cast<const char*>(data);
            std::copy(d, d + len, begin() + read_index_);
        }

        void Retrieve(size_t len)
        {
            assert(readablebytes() >= len);
            if(len < readablebytes())
            {
                read_index_ += len;
            }
            else
            {
                RetrieveAll();
            }
        }

        void RetrieveUntilIndex(const char* index)
        {
            assert(beginWrite() >= index);

            read_index_ += index - beginread();
        }

        void RetrieveInt64()
        {
            Retrieve(sizeof(int64_t));
        }

        void RetrieveInt32()
        {
            Retrieve(sizeof(int32_t));
        }

        void RetrieveInt16()
        {
            Retrieve(sizeof(int16_t));
        }

        void RetrieveInt8()
        {
            Retrieve(sizeof(int8_t));
        }

        void RetrieveAll()
        {
            write_index_ = kPrePendIndex;
            read_index_ = kPrePendIndex;
        }

        std::string RetrieveAsString(size_t len)
        {
            assert(len <= readablebytes());
            
            std::string ret = std::move(PeekAsString(len));
            Retrieve(len);
            return ret;
        }

        std::string RetrieveAllAsString()
        {
            std::string ret = std::move(PeekAllAsString());
            RetrieveAll();
            return ret;
        }

        const char* Peek() const 
        {
            return beginread();
        }

        std::string PeekAsString(int len)
        {
            return std::string(beginread(), beginread() + len);
        }

        std::string PeekAllAsString()
        {
            return std::string(beginread(), beginWrite());
        }

        size_t readablebytes() const { return write_index_ - read_index_; }
        size_t writablebytes() const { return buffer_.size() - write_index_; }
        size_t prependablebytes() const { return read_index_; }

        void MakeSureEnoughStorage(int len)
        {
            if(writablebytes() >= len) return;

            if(writablebytes() + prependablebytes() >= kPrePendIndex + len)
            {
                std::copy(beginread(), beginWrite(), begin() + kPrePendIndex);
                write_index_ = kPrePendIndex + readablebytes();
                read_index_ = kPrePendIndex;
            }
            else
            {
                buffer_.resize(write_index_ + len);
            }
        }

    private:
        char* begin() { return &*buffer_.begin(); }
        const char* begin() const { return &*buffer_.begin(); }

        char* beginread() { return begin() + read_index_; }
        const char* beginread() const { return begin() + read_index_; }

    private:
        std::vector<char> buffer_;
        size_t read_index_;
        size_t write_index_;
    };
}
#endif
